<?php

/**
 * Base components for the jfPortableDevicePlugin jfPortableDevice module.
 * 
 * @package     jfPortableDevicePlugin
 * @subpackage  jfPortableDevice
 * @author      Jonas Fischer <info@jonasfischer.net>
 * @version     SVN: $Id: BaseActions.class.php 12534 2008-11-01 13:38:27Z Kris.Wallsmith $
 */
abstract class BasejfPortableDeviceComponents extends sfActions
{

  public function executeIncludeAssetsForPage() {
  	$request = sfContext::getInstance()->getRequest(); //symfony 1.0 compatibility
	  $response = sfContext::getInstance()->getResponse();
		$this->isNativeApp = jfPortableDevice::isNativeApp();
//	  var_dump($response->getStylesheets(), $this->excludeStylesheets);
//	  var_dump($response->getJavascripts(), $this->excludeJavascripts);
//	  die();

		$excludeStylesheets = array_merge(jfPortableDevice::getConfig('includeStylesheetsForApp'), jfPortableDevice::getConfig('excludeStylesheets'));
		foreach ($excludeStylesheets as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->removeStylesheet($filename, $position);
		}
		
		$excludeJavascripts = array_merge(jfPortableDevice::getConfig('includeJavascriptsForApp'), jfPortableDevice::getConfig('excludeJavascripts'));
		foreach ($excludeJavascripts as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->removeJavascript($filename, $position);
		}

		if ($this->isNativeApp) {
			$this->buildSettings = jfPortableDevice::getConfig('buildSettings');
			$this->prepareAssetsForNativeApp($request, $response, $this->buildSettings);
		}
  }
  
  public function executeIncludeAssetsForApp() {
  	$request = sfContext::getInstance()->getRequest(); //symfony 1.0 compatibility
	  $response = sfContext::getInstance()->getResponse();
	  $this->isNativeApp = jfPortableDevice::isNativeApp();

	  $this->removeAllStylesheetsFromResponse();
		$includeStylesheets = jfPortableDevice::getConfig('includeStylesheetsForApp');
		foreach ($includeStylesheets as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->addStylesheet($filename, $position);
		}
		
		$this->removeAllJavascriptsFromResponse();
		$includeJavascripts = jfPortableDevice::getConfig('includeJavascriptsForApp');
		foreach ($includeJavascripts as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->addJavascript($filename, $position);
		}

		if ($this->isNativeApp) {		
			$this->buildSettings = jfPortableDevice::getConfig('buildSettings');
			$this->prepareAssetsForNativeApp($request, $response, $this->buildSettings);
		}
  }
  
  protected function prepareAssetsForNativeApp(sfWebRequest $request, sfWebResponse $response, $buildSettings) {
    $this->isInitialRequest = !$request->isXmlHttpRequest();
    foreach ($buildSettings['javascripts'] as $file) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($file);
      if ($this->isInitialRequest) {
        $response->addJavascript($filename, $position);
      }
      else {
        $response->removeJavascript($filename, $position);
      }
    }

    foreach ($buildSettings['stylesheets'] as $file) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($file);
      if ($this->isInitialRequest) {
        $response->addStylesheet($filename, $position);
      }
      else {
        $response->removeStylesheet($filename, $position);
      }
    }
    
  	$excludeStylesheets = $buildSettings['excludeStylesheets'];
		foreach ($excludeStylesheets as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->removeStylesheet($filename, $position);
		}
		
		$excludeJavascripts = $buildSettings['excludeJavascripts'];
		foreach ($excludeJavascripts as $assetResource) {
      list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
		  $response->removeJavascript($filename, $position);
		}

		
    $context = $this->getContext();
    if(method_exists($context, 'getConfiguration') && method_exists($context->getConfiguration(), 'loadHelpers')) {
		$context->getConfiguration()->loadHelpers('Asset');
    }
    else if (method_exists('sfLoader', 'loadHelpers')) {
      sfLoader::loadHelpers('Asset'); //compatibility with symfony 1.0:
    }
    
    sfConfig::set('symfony.asset.stylesheets_included', true);
    sfConfig::set('symfony.asset.javascripts_included', true);
    
    $css = '';
    $js = '';
    foreach (array('first', '', 'last') as $position) {
    	
    	$stylesheets = $response->getStylesheets($position);
    	if (!empty($stylesheets)) {
	      $css .= "\n/* position: $position */\n";
	      $css .= $this->getCombinedAssetsAsString($stylesheets, 'stylesheet_path');
    	}
      
      
      $javascripts = $response->getJavascripts($position);
      if (!empty($javascripts)) {
	      $jsCurrent = $this->getCombinedAssetsAsString($javascripts, 'javascript_path');
	      $jsCurrent = str_replace(array("'<script>", "</script>'"), array("'<scr'+'ipt>", "</scr'+'ipt>'"), $jsCurrent); //hack to avoid breaking code in jQuery mobile: domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' );
	      $js .= "\n/* position: $position */\n";
	      $js .= $jsCurrent;
      }
    }
    
    if ($this->isInitialRequest) { //embed everything directly into main file
    	
    	$css = $this->base64EncodeImages($css);
    	
      $this->cssContent = $css;
      $this->jsContent = $js;
      $this->jsContent2 = 'test';
    }
    else {
      $this->cssSrc = $this->getContentAsFileSrc($css, 'css');
      if (!$this->cssSrc) {
        $this->cssContent = $css;
      }
      
//		    $this->jsSrc = $this->getContentAsFileSrc($js, 'js');
//		    if (!$this->jsSrc) {
//		    	$this->jsContent = $js;
//		    }
        $this->jsContent = $js; //we have to load javascripts inline because iOs Safari loads external javascripts asynchronesly and thus we could nomore rely on events like pagecreate, pageshow, pagehide dom.ready etc
    }
  }
  
  protected function removeAllStylesheetsFromResponse() {
  	$response = $this->getResponse();
	  foreach ($response->getPositions() as $position) {
	  	foreach ($response->getStylesheets($position) as $filename => $assetResource) {
	  		if (!is_string($filename) && !empty($assetResource)) {
	  			list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
	  		}
	  		$response->removeStylesheet($filename, $position);
	  	}
	  }
  }

  protected function removeAllJavascriptsFromResponse() {
  	$response = $this->getResponse();
	  foreach ($response->getPositions() as $position) {
	  	foreach ($response->getJavascripts($position) as $filename => $assetResource) {
	  		if (!is_string($filename) && !empty($assetResource)) {
	  			list($filename, $position) = $this->extractFilenameAndPositionForAssetResource($assetResource);
	  		}
	  		$response->removeJavascript($filename, $position);
	  	}
	  }
  }
  
  /**
   * Returns the url to the js/css file containing the given {@param $content].
   * Stores the content into a new file if it does not exist already.
   * 
   * This is done to allow the js/css contents to be cached
   * 
   * @param String $content
   * @param String $type [js|css]
   */
  protected function getContentAsFileSrc($content, $type) {
  	if (empty($content)) {
  		return false;
  	}
  	$hash = md5($content);
  	$buildSettings = jfPortableDevice::getConfig('buildSettings');
  	$uploadDir = $buildSettings['uploadDir'];
  	$uploadUrl = $buildSettings['uploadUrl'];

  	$filename = $hash.'.'.$type;
  	if (!file_exists($uploadDir.$filename)) {
  		if (!file_exists($uploadDir)) {
	  		mkdir($uploadDir, 0755, true);
  		}
  		
  		$result = file_put_contents($uploadDir.$filename, $content);
  		if ($result) {
  			return $uploadUrl.$filename;
  		}
  	}
  	
  	return false;
  }
  
  /**
   * Combine all given {@param $assets} into one single css/js string.
   * This is done because Safari on iOS does not ensure the script execution order for javascripts loaded via ajax.
   * 
   * @param array $assets
   * @param String $computePathCallback
   */
  protected function getCombinedAssetsAsString($assets, $computePathCallback) {
  	$result = '';
		foreach ($assets as $file => $options) {
			$result .= "\n\n/* $file */\n\n";
			if (0 === strpos($file, 'http')) {
        $assetUrl = $file;
      }
      else {
				$assetUrl = call_user_func_array($computePathCallback, array($file, true));
        $assetUrl = $this->adjust_asset_url($assetUrl);
			}
      $assetUrlPrefix = dirname($assetUrl).'/';

			$assetUrlParsed = parse_url($assetUrlPrefix);
			$assetUrlRoot = $assetUrlParsed['scheme'].'://'.$assetUrlParsed['host'].'/';

			$resultAbsoluteUrls = str_replace(
				array(
				          'url("data:', "url('data:", 'url(data:',
				),
				array(
				          '###MASK_URL_DATA_DOUBLE_QUOTE###', '###MASK_URL_DATA_SINGLE_QUOTE###', '###MASK_URL_DATA_NO_QUOTE###',
				)
				, file_get_contents($assetUrl));
			
      $resultAbsoluteUrls = str_replace(
        array(
          'url("/', "url('/", 'url(/',
        ),
        array(
          '###MASK_URL_ROOT_DOUBLE_QUOTE###', '###MASK_URL_ROOT_SINGLE_QUOTE###', '###MASK_URL_ROOT_NO_QUOTE###',
        )
        , $resultAbsoluteUrls);

      $resultAbsoluteUrls = str_replace(
        array(
          'url("http', "url('http", 'url(http',
        ),
        array(
          '###MASK_URL_HTTP_DOUBLE_QUOTE###', '###MASK_URL_HTTP_SINGLE_QUOTE###', '###MASK_URL_HTTP_NO_QUOTE###',
        )
        , $resultAbsoluteUrls);

      $resultAbsoluteUrls = str_replace(
        array(
          'url("', "url('", 'url(',
        ),
        array(
          '###MASK_URL_PREFIX_DOUBLE_QUOTE###', '###MASK_URL_PREFIX_SINGLE_QUOTE###', '###MASK_URL_PREFIX_NO_QUOTE###',
        )
        , $resultAbsoluteUrls);
      
      $resultAbsoluteUrls = str_replace(
	      array(
	        '###MASK_URL_DATA_DOUBLE_QUOTE###', '###MASK_URL_DATA_SINGLE_QUOTE###', '###MASK_URL_DATA_NO_QUOTE###',
	      ),
	      array(
	        'url("data:', "url('data:", 'url(data:',
	      )
      	, $resultAbsoluteUrls);
      
      $resultAbsoluteUrls = str_replace(
        array(
          '###MASK_URL_ROOT_DOUBLE_QUOTE###', '###MASK_URL_ROOT_SINGLE_QUOTE###', '###MASK_URL_ROOT_NO_QUOTE###',
        ),
        array(
          'url("'.$assetUrlRoot, "url('".$assetUrlRoot, 'url('.$assetUrlRoot,
        )
        , $resultAbsoluteUrls);

      $resultAbsoluteUrls = str_replace(
        array(
          '###MASK_URL_HTTP_DOUBLE_QUOTE###', '###MASK_URL_HTTP_SINGLE_QUOTE###', '###MASK_URL_HTTP_NO_QUOTE###',
        ),
        array(
          'url("http', "url('http", 'url(http',
        )
        , $resultAbsoluteUrls);

      $resultAbsoluteUrls = str_replace(
        array(
          '###MASK_URL_PREFIX_DOUBLE_QUOTE###', '###MASK_URL_PREFIX_SINGLE_QUOTE###', '###MASK_URL_PREFIX_NO_QUOTE###',
        ),
        array(
          'url("'.$assetUrlPrefix, "url('".$assetUrlPrefix, 'url('.$assetUrlPrefix,
        )
        , $resultAbsoluteUrls);
        
      $result .= $resultAbsoluteUrls;
			
		}
		return $result;
  }
 
  protected function base64EncodeImages($css) {
  	if (strlen($css)) {
	  	$matches = array();
	  	preg_match_all('~\\(["|\']?(http://.+)["|\']?\\)~U', $css, $matches);
	  	foreach ($matches[1] as $url) {
	  		if (false !== strpos($css, $url)) { //check if url has not already been encoded to avoid unnecessary encoding requests
	  			$css = str_replace($url, $this->base64EncodeImage($url), $css);
	  		}
	  	}
  	}

  	return $css;
  }
  
  private function base64EncodeImage($filename) {
  	if (false === strpos($filename, 'jfPD_embeddingDisabled')) {
  		$filetype = $this->getFiletype($filename);
  		if ($filetype) {
	 			$binary = @file_get_contents($filename);
	 			if($binary) {
	 				return 'data:image/' . $filetype . ';base64,' . base64_encode($binary);
	 			}
	 		}
  	}

		return $filename;
  }
  
  private function getFiletype($url) {
  	$urlParsed = parse_url($url);
  	$pathInfo = pathinfo($urlParsed['path']);
  	return $pathInfo['extension'];
  }
  
  /**
   * - add htaccess password information to url (if required)
   * 
   * @param String $url
   *
   * @return String
   */
  protected function adjust_asset_url($url) {
		$url = $this->addAuthenticationCredentialsToUrl($url);
    return $url;
  }
  
  /**
   * add htaccess password information to url (if required)
   * 
   * @param String $url
   *
   * @return String
   */
  protected function addAuthenticationCredentialsToUrl($url) {
  	if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
  		$url = str_replace('http://', 'http://'.$_SERVER['PHP_AUTH_USER'].':'.$_SERVER['PHP_AUTH_PW'].'@', $url);
  		$url = str_replace('https://', 'https://'.$_SERVER['PHP_AUTH_USER'].':'.$_SERVER['PHP_AUTH_PW'].'@', $url);
  	}
    return $url;
  }
  
  protected function extractFilenameAndPositionForAssetResource($assetResource) {
    if (is_array($assetResource)) {
      foreach ($assetResource as $filename => $options) {
        $position = '';
        if (is_array($options) && array_key_exists('position', $options)) {
          $position = $options['position'];
          break;
        }
      }
    }
    else {
      $position = '';
      $filename = $assetResource;
    }
    
    return array($filename, $position);
  }

  
  public function executeInitialize() {
  	$request = sfContext::getInstance()->getRequest(); //symfony 1.0 compatibility
		$this->config = json_encode($this->getJsConfig($request));
  }
  
	protected function getJsConfig(sfWebRequest $request)
  {
  	
  	$context = $this->getContext();
  	// compatibility with symfony 1.0:
  	if(method_exists($context, 'getConfiguration') && method_exists($context->getConfiguration(), 'loadHelpers')) {
      $context->getConfiguration()->loadHelpers('Url');
    }
    else if (method_exists('sfLoader', 'loadHelpers')) {
      sfLoader::loadHelpers('Url'); //maintain compatibility to symfony versions prior to 1.3
    }
  	
    $config = array();
    $config['displayErrors'] = sfConfig::get('sf_environment') === 'dev';
    $config['appVersion'] = jfPortableDevice::getConfig('appVersion', 1);
    $config['startUrl'] = url_for(jfPortableDevice::getConfig('startUrl'));
    $config['messages'] = jfPortableDevice::getConfig('messages', array());
    $config['environment'] = jfPortableDevice::getConfig('environment', array());

    
    try {
    	$i18n = sfContext::getInstance()->getI18N();
	    foreach ($config['messages'] as $key => $value) {
	    	$config['messages'][$key] = $i18n->__($value, null, 'jfPortableDevice');
	    }
    }
    catch (sfConfigurationException $e) {
  		//i18n not enabled. That'S OK. We'll simply use the english message.
  	}
  	
  	$config['refreshTimeout'] = jfPortableDevice::getConfig('refreshTimeout', 0);
  	$config['refreshUrls'] = array_map('url_for', jfPortableDevice::getConfig('refreshUrls', array()));
    

    return $config;
  }
  
}
